
/*----------- Stage -----------*/
function Stage(canvas) {
    this.canvas = canvas;
    this.targetInterval = 15;
    this.scenes = [];
    this.currentScene = 0;
    this.frameCount = 0;
    this.fps = 0;
    this.width = canvas.width;
    this.height = canvas.height;

    if (this.canvas.getContext) {
        this.context = this.canvas.getContext('2d');
    } else {
        alert('This requires a browser that supports the <canvas> tag!');
    }

    this.keymap = {};
    for (var i = 0; i < 256; i++) {
        this.keymap[i] = 0;
    }

    var self = this;
    this.canvas.addEventListener('click', function(e) {
        var pos = getCursorPos(e, self.canvas);
        var currScene = self.scenes[self.currentScene];
        if (currScene.click) {
            currScene.click(pos.x, pos.y);
        }
    }, false);

    this.canvas.addEventListener('dblclick', function(e) {
        var pos = getCursorPos(e, self.canvas);
        var currScene = self.scenes[self.currentScene];
        if (currScene.dblclick) {
            currScene.dblclick(pos.x, pos.y);
        }
    }, false);

    this.canvas.addEventListener('mouseover', function(e) {
        var pos = getCursorPos(e, self.canvas);
        var currScene = self.scenes[self.currentScene];
        if (currScene.mouseover) {
            currScene.mouseover(pos.x, pos.y);
        }
    }, false);

    this.canvas.addEventListener('mousemove', function(e) {
        var pos = getCursorPos(e, self.canvas);
        var currScene = self.scenes[self.currentScene];
        if (currScene.mousemove) {
            currScene.mousemove(pos.x, pos.y);
        }
    }, false);

    this.canvas.addEventListener('keypress', function(e) {
        var currScene = self.scenes[self.currentScene];
        if (currScene.keypress) {
            currScene.keypress(e.which);
        }
    }, false);

    this.canvas.addEventListener('keydown', function(e) {
        var currScene = self.scenes[self.currentScene];
        if (self.keymap[e.which] == 0) {
            self.keymap[e.which] = self.currentTime;
        }
        if (currScene.keydown) {
            currScene.keydown(e.which);
        }
    }, false);

    this.canvas.addEventListener('keyup', function(e) {
        var currScene = self.scenes[self.currentScene];
        self.keymap[e.which] = false;
        if (self.keymap[e.which] > 0) {
            self.keymap[e.which] = 0;
        }
        if (currScene.keyup) {
            currScene.keyup(e.which);
        }
    }, false);
}

Stage.prototype.start = function() {

    if (!this.scenes[this.currentScene]) {
        alert('No current scene');
        return;
    }

    this.context.fillStyle = "#FFFFFF";
    this.context.font = "11px Courier New";

    this.scenes[this.currentScene].init();

    var self = this;
    setInterval(function() {
        self.__updateAndRender();
    }, this.targetInterval);

    setInterval(function() {
        self.fps = self.frameCount;
        self.frameCount = 0;
    }, 1000);

};

Stage.prototype.addScene = function(scene) {
    this.scenes.push(scene);
};

Stage.prototype.setCurrentScene = function(index) {
    this.currentScene = index;
};

Stage.prototype.__updateAndRender = function() {
    this.currentTime = getMillis();
    this.frameCount++;

    this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);

    this.scenes[this.currentScene].update();
    this.scenes[this.currentScene].render(this.context);

    this.context.fillText("fps: " + this.fps, 2, 10);
};

/*----------- end Stage -----------*/
/*----------- utilities -----------*/

function getMillis() {
    return new Date().getTime();
}

function getCursorPos(e, elem) {
    var x;
    var y;
    if (e.pageX || e.pageY) {
        x = e.pageX - elem.offsetLeft;
        y = e.pageY - elem.offsetTop;
    } else {
        x = e.clientX + document.body.scrollLeft
        + document.documentElement.scrollLeft - elem.offsetLeft;
        y = e.clientY + document.body.scrollTop
        + document.documentElement.scrollTop - elem.offsetTop;
    }
    return {
        x: x,
        y: y
    };
}

/*----------- end utilities -----------*/
/*----------- AnimatedSprite -----------*/
function AnimatedSprite(image, options) {
    this.image = image;
    this.cellWidth = options.cellWidth;
    this.cellHeight = options.cellHeight;
    this.x = 0;
    this.y = 0;
    this.offsetX = options.offsetX;
    this.offsetY = options.offsetY;
    this.frames = options.frames;
    this.gridSpace = 0;
    this.timer = 0;
    this.interval = options.interval;

    if (options.gridSpace !== undefined) {
        this.gridSpace = options.gridSpace;
    }

    var len = options.frames.length;
    for (var i = 0; i < len; i++) {
        this.frames[i].x = options.frames[i].x * (options.cellWidth + this.gridSpace);
        this.frames[i].y = options.frames[i].y * (options.cellHeight + this.gridSpace);
    }

    this.cellX = this.frames[0].x;
    this.cellY = this.frames[0].y;
}

AnimatedSprite.prototype.playOnce = function() {
    var index = 0;
    var self = this;
    var frames = this.frames;
    var len = this.frames.length;

    if (this.timer == 0) {
        this.timer = setInterval(function() {
            self.cellX = frames[index].x;
            self.cellY = frames[index].y;
            index++;
            if (index >= len) {
                clearInterval(self.timer);
                self.timer = 0;
                if (self.onPlayEnd !== undefined) {
                    self.onPlayEnd();
                }
            }
        }, this.interval);
    }
}

AnimatedSprite.prototype.playLoop = function() {
    var index = 0;
    var self = this;
    var frames = this.frames;
    var len = this.frames.length;

    this.timer = setInterval(function() {
        self.cellX = frames[index].x;
        self.cellY = frames[index].y;
        index++;
        if (index >= len) {
            index = 0;
        }
    }, this.interval);
}

AnimatedSprite.prototype.stop = function() {
    clearInterval(this.timer);
    this.timer = 0;
};

AnimatedSprite.prototype.render = function(context) {
    context.drawImage(this.image, this.cellX, this.cellY, this.cellWidth,
        this.cellHeight, this.x - this.offsetX, this.y - this.offsetY,
        this.cellWidth, this.cellHeight);
};
/*----------- end AnimatedSprite -----------*/

/*----------- ImageSprite -----------*/
function ImageSprite(image, options) {
    this.image = image;
    this.x = 0;
    this.y = 0;
    this.gridSpace = options.gridSpace;
    this.render = function(context) {
    };

    if (options == null) {
        this.render = function(context) {
            context.drawImage(this.image, this.x, this.y);
        };
    } else {
        var offsetX = options.offsetX;
        var offsetY = options.offsetY;
        this.cellWidth = options.cellWidth;
        this.cellHeight = options.cellHeight;
        this.cellX = options.x * (this.cellWidth + this.gridSpace);
        this.cellY = options.y * (this.cellHeight + this.gridSpace);

        this.render = function(context) {
            context.drawImage(this.image, this.cellX, this.cellY,
                this.cellWidth, this.cellHeight, this.x - offsetX, this.y
                - offsetY, this.cellWidth, this.cellHeight);
        };
    }
}

/*----------- end ImageSprite -----------*/
/*----------- start CollisionManager -----------*/

function CollisionManager() {
    this.objects = [];
}

CollisionManager.prototype.update = function() {
    var objects = this.objects;
    for (var i = 0; i < objects.length; i++) {
        var a = objects[i];
        var ax1 = a.x + a.dx - a.halfWidth;
        var ax2 = a.x + a.dx + a.halfWidth;
        var ay1 = a.y + a.dy - a.halfHeight;
        var ay2 = a.y + a.dy + a.halfHeight;
        if (ax1 < 0 || ax2 > STAGE_MAX_X || ay1 < 0 || ay2 > STAGE_MAX_Y) {
            if (a.onCollide) {
                a.onCollide(null);
            }
        }
       
        for (var j = 0; j < objects.length; j++) {
            var b = objects[j];
            if (a !== b) {
                var bx1 = b.x + b.dx - b.halfWidth;
                var bx2 = b.x + b.dx + b.halfWidth;
                var by1 = b.y + b.dy - b.halfHeight;
                var by2 = b.y + b.dy + b.halfHeight;
                if (ax1 < bx2 && ax2 > bx1 &&
                    ay1 < by2 && ay2 > by1) {
                    if (a.onCollide) {
                        a.onCollide(b);
                    }
                }
            }
        }
    }
}

CollisionManager.prototype.clean = function() {
    this.objects = this.objects.filter(function(elem) {
        return !elem.remove;
    });
}

/*----------- end CollisionManager -----------*/